3. Property 읽기, 쓰기 자동화(또는 단순화)
enum class EPropertyType : int
{
EPT_Float = 0,
EPT_Boolean,
EPT_String,
EPT_Float3,
EPT_StringArray,
EPT_TypeNum
};
struct FProperty
{
using FPropertyValue = std::variant<
float,
XMFLOAT3,
bool,
std::string,
std::vector<std::string>
>;
std::string Name;
EPropertyType Type;
FPropertyValue Value;
};
위와 같이 Enum과 구조체, std::variant를 활용해서 프로퍼티를 읽고, 저장하고 있다. 문제는, 이것을 처리하는 것이아래와 같이 if, else로 이루어진 지옥문 코딩이라는 점이다.
void FBlueprintAsset::SerializeProperty(FXMLElement* PropertyElement, FBinaryWriter& Writer)
{
const std::string PropertyType = PropertyElement->Name();
Writer << PropertyElement->Attribute("Name");
if (PropertyType == "float")
{
Writer << (int)EPropertyType::EPT_Float;
const float Value = PropertyElement->FloatAttribute("Value");
Writer << Value;
}
else if (PropertyType == "float3")
{
Writer << (int)EPropertyType::EPT_Float3;
std::string Float3 = PropertyElement->Attribute("Value");
XMFLOAT3 Value;
sscanf_s(Float3.c_str(), "(%f, %f, %f)", &Value.x, &Value.y, &Value.z);
Writer << Value;
}
else if (PropertyType == "bool")
{
...
}
이런식으로 처리되고 있는 함수가 총 6개로, 새로운 타입을 추가할때마다 매번 처리해주어야 하는 비효율의 끝을 보여주고 있다.
그래서 C++의 Template 특수화, 폴드 표현식, 그리고 std::variant와 std::visit을 최대한 활용해서 이를 자동화 해보자고 한다.
아래와 같이 템플릿 특수화로 구조체를 만들어 주고,
template<typename T> struct PropertyTraits;
template<> struct PropertyTraits<bool>
{
static constexpr const char* Tag = "bool";
static constexpr EPropertyType Type = EPropertyType::EPT_Boolean;
static bool Parse(FXMLElement* Element) { return Element->BoolAttribute("Value"); }
};
템플릿과 폴드 표현식으로 아래와 같이 함수를 if, else를 제거하고 깔끔하게 만들 수 있다.
template<typename... Args>
void SerializePropertyHelper(FXMLElement* PropertyElement, FBinaryWriter& Writer, std::variant<Args...>)
{
std::string Name = PropertyElement->Attribute("Name");
Writer << Name;
const std::string Tag = PropertyElement->Name();
// 폴드 표현식
bool bSuccess = ((Tag == PropertyTraits<Args>::Tag ?
(Writer << Type, Writer << PropertyTraits<Args>::Parse(PropertyElement), true) : false ||
...);
assert(bSuccess && "Invalid property type");
}
void FBlueprintAsset::SerializeProperty(FXMLElement* PropertyElement, FBinaryWriter& Writer)
{
SerializePropertyHelper(PropertyElement, Writer, FProperty::FPropertyValue{});
}
3.3. variant와 visit를 참고해서 visit을 활용해서 if, else를 줄일 수 있다.